import { assetManager, AssetManager, Game, game, path, sys } from 'cc';
import AssetsBundle, { IAssetsBundleManager } from './AssetsBundle';
import { DEBUG } from 'cc/env';

enum BundleReleaseAction {
    Default,     // 默认， 根据开关决定是否释放
    DontRelease, // 不释放， 设置setAutoRelease(false)
    DontCache,   // 不缓存，直接释放
}

const AdminBundleName = 'app-admin';
const ModelBundleName = 'app-model';
const ControlBundleName = 'app-control';
const ControllerBundleName = 'app-controller';
const ManagerBundleName = 'app-manager';
const BuiltinBundleName = 'app-builtin';

export default class AssetsBundleManager implements IAssetsBundleManager {
    static settings = {
        // 是否开启Bundle缓存
        enableCache: true,
        // 内存检测的间隔时间(ms)
        memoryCheckInterval: 1000,
        // 内存检测的阈值（B）
        memoryCheckThreshold: 800 * 1024 * 1024,
    };

    private static _instance: AssetsBundleManager;
    public static get instance(): AssetsBundleManager|null {
        return AssetsBundleManager._instance;
    }

    private static _testUsedMemory: number = undefined;
    public static EnableGetUsedMemory(): boolean {
        if (this._testUsedMemory === undefined) {
            this._testUsedMemory = this.GetUsedMemory();
        }
        return this._testUsedMemory >= 0;
    }

    public static SupportLowMemoryWarning(): boolean {
        return !sys.isBrowser;
    }

    public static EnableChache(): boolean {
        return this.settings.enableCache && (this.SupportLowMemoryWarning() || this.EnableGetUsedMemory());
    }

    public static GetUsedMemory(): number {
        if (sys.isBrowser) {
            const performance = window.performance as any;
            const memory = performance?.memory;
            return memory ? memory.usedJSHeapSize : -1;
        } else if (sys.platform === sys.Platform.WECHAT_GAME) {
            const wx = window.wx as any;
            const perf = wx && wx.getPerformance && wx.getPerformance();
            const memory = perf && perf.memory;
            return memory? memory.usedJSHeapSize : -1;
        } else if (sys.platform === sys.Platform.BYTEDANCE_MINI_GAME) {
            const tt = window.tt as any;
            const perf = tt && tt.getPerformance && tt.getPerformance();
            const memory = perf && perf.memory;
            return memory? memory.usedJSHeapSize : -1;
        }
        return -1;
    }

    private _deps: Map<string, string[]>;
    private _bundles: Map<string, AssetsBundle>;
    private _releaseActions: Map<string, BundleReleaseAction>;
    private _waitGCTimer: any = undefined;

    constructor(deps: {[key: string]: string[]}, releaseActions: {[key: string]: BundleReleaseAction}){
        this._deps = new Map();
        this._releaseActions = new Map();
        for (const key in deps) {
            this._deps.set(key, deps[key].slice());
        }
        for (const key in releaseActions) {
            this._releaseActions.set(key, releaseActions[key]);
        }
        this._releaseActions.set(AdminBundleName, BundleReleaseAction.DontRelease);
        this._releaseActions.set(ModelBundleName, BundleReleaseAction.DontRelease);
        this._releaseActions.set(ControlBundleName, BundleReleaseAction.DontRelease);
        this._releaseActions.set(ControllerBundleName, BundleReleaseAction.DontRelease);
        this._releaseActions.set(ManagerBundleName, BundleReleaseAction.DontRelease);
        this._releaseActions.set(BuiltinBundleName, BundleReleaseAction.DontRelease);
        this._bundles = new Map();

        if (AssetsBundleManager.EnableChache()) {
            setInterval(()=>{
                this.memoryCheck();
            }, AssetsBundleManager.settings.memoryCheckInterval);

            game.on(Game.EVENT_LOW_MEMORY, this.onMemoryWarning, this);
        }

        return AssetsBundleManager._instance = this;
    }

    hasDependencies(name: string): boolean {
        name = path.basename(name);
        return this._deps.has(name);
    }

    getDependencies(name: string): string[] {
        name = path.basename(name);
        return this._deps.get(name) ?? [];
    }

    getAssetsBundle(name: string): AssetsBundle | null {
        return this._bundles.get(path.basename(name)) ?? null;
    }

    addAssetsBundle(name: string, bundle: AssetManager.Bundle): AssetsBundle {
        name = path.basename(name);
        if (this._bundles.has(name)) {
            console.error(`Same name with ${name} bundle is loaded!`)
            return this._bundles.get(name);
        }
        const ab = new AssetsBundle(this, bundle);
        this._bundles.set(name, ab);
        ab.onReleaseCallback(()=>{
            this.removeAssetsBundle(name);
        });
        const action = this.getBundleReleaseAction(name);
        if (action === BundleReleaseAction.DontRelease) {
            ab.setAutoRelease(false);
        } else if (action === BundleReleaseAction.Default) {
            if (AssetsBundleManager.EnableChache()) {
                ab.addRef();
            }
        }
        return ab;
    }

    public release(name: string): void {
        name = path.basename(name);
        const bundle = this.getAssetsBundle(name);
        if (bundle) {
            bundle.release();
        } else {
            const origin = assetManager.getBundle(name);
            if (origin) origin.releaseAll();
        }
    }

    private removeAssetsBundle(name: string): void {
        if (this._bundles.has(name)) {
            this._bundles.delete(name);
        }
    }

    private getBundleReleaseAction(name: string): BundleReleaseAction {
        if (this._releaseActions.has(name)) {
            return this._releaseActions.get(name);
        }
        return BundleReleaseAction.Default;
    }

    public memoryCheck(): void {
        if (this._waitGCTimer !== undefined) return;

        const usedMemory = AssetsBundleManager.GetUsedMemory();
        if (usedMemory < AssetsBundleManager.settings.memoryCheckThreshold) return;
        this.releaseAllCache();
    }

    private onMemoryWarning(): void {
        if (this._waitGCTimer !== undefined) return;
        this.releaseAllCache();
    }

    private releaseAllCache(): void {
        if (!AssetsBundleManager.EnableChache()) return;
        let releasing = false;
        for (const [name, bundle] of this._bundles) {
            if (!bundle.canBeRelease) continue
            const action = this.getBundleReleaseAction(name);
            if (action === BundleReleaseAction.Default) {
                if (bundle.release())
                    releasing = true;
            }
        }
        if (releasing) {
            this._waitGCTimer = setTimeout(()=>{
                this._waitGCTimer = undefined;
            }, 500);
        }
    }
}

if (DEBUG) {
    window['AssetsBundleManager'] = AssetsBundleManager;
}

